<?php

namespace Common\Lib;

class WxOauth
{
    /**
     * 用户同意授权，获取 code url
     */
    const WX_OAUTH_BASE_URL = 'https://open.weixin.qq.com/connect/oauth2/authorize';

    /**
     * 通过code换取网页授权 access_token url
     */
    const WX_ACCESS_TOKEN_URL = 'https://api.weixin.qq.com/sns/oauth2/access_token';

    /**
     *  拉取用户信息url
     */
    const WX_USERINFO_URL = 'https://api.weixin.qq.com/sns/userinfo';

    /**
     * 重定向state参数
     */
    const WX_STATE_LEN_LIMIT = 128;

    /**
     * 用户刷新access_token有效期
     */
    const REFRESH_TOKEN_LIMIT_TIME = 2592000;  // 30天

    /**
     * 微信服务号配置信息 appid secret
     * @var array
     */
    protected $config;

    /**
     * @var string
     */
    protected $openid;

    /**
     * @var string
     */
    protected $accessToken;

    /**
     * WxOauth constructor.
     */
    public function __construct($config = null)
    {
        if (!isset($config)) {
            $this->config = array(
                'appid'  => C('YTATA_WEIXIN_APPID'),
                'secret' => C('YTATA_WEIXIN_APPSECRET')
            );
        } else {
            $this->config = $config;
        }
    }

    /**
     * @return string
     * @throws \Exception
     */
    public function getOpenid($scope = 'snsapi_base')
    {
        //通过code获得openid
        if (!isset($_GET['code'])) {
            //触发微信返回code码
            $baseUrl = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . '?' . $_SERVER['QUERY_STRING'];
            $url     = $this->createAuthorizeUri($baseUrl, $scope);
            Header("Location: $url");
            exit();
        } else {
            //获取code码，以获取openid
            $code   = $_GET['code'];
            $openid = $this->getOpenidFromMp($code);
            return $openid;
        }
    }

    /**
     * 获取用户信息
     * @return mixed
     * @throws \Exception
     */
    public function getUserInfo()
    {
        $accessToken = $this->accessToken;
        $openid      = $this->openid;

        $ret = $this->httpsRequest($this->createUserInfoUri($accessToken, $openid));
        if (isset($ret['errcode'])) {
            throw new \Exception($ret['errmsg']);
        }

        return $ret;
    }

    /**
     * 生成微信授权请求URL
     *
     * @param string $redirectUri
     * @param string $scope
     * @param string $state
     *
     * @return string
     */
    protected function createAuthorizeUri($redirectUri, $scope = 'snsapi_base', $state = '')
    {
        $args['appid']         = $this->config['appid'];
        $args['redirect_uri']  = $redirectUri;
        $args['response_type'] = 'code';
        $args['scope']         = $scope;

        if (!empty($state)) {
            if (!is_string($state) || strlen($state) > self::WX_STATE_LEN_LIMIT) {
                throw new \InvalidArgumentException('重定向state参数错误');
            }
            $args['state'] = $state;
        }

        return self::WX_OAUTH_BASE_URL . '?' . http_build_query($args) . '#wechat_redirect';
    }

    /**
     * 从公众号获取授权信息
     * @param string $code
     * @return string openid
     * @throws \Exception
     */
    protected function getOpenidFromMp($code)
    {
        $ret = $this->httpsRequest($this->createAccessTokenUri($code));
        if (isset($ret['errcode'])) {
            throw new \Exception($ret['errmsg']);
//            die($ret['errmsg']);
        }

        $this->openid      = $ret['openid'];
        $this->accessToken = $ret['access_token'];

        return $ret['openid'];
    }

    /**
     * 生成获取用户信息URL
     * @param string $accessToken
     * @param string $openid
     * @param string $lang
     * @return string
     */
    protected function createUserInfoUri($accessToken, $openid, $lang = 'zh_CN')
    {
        $args['access_token'] = $accessToken;
        $args['openid']       = $openid;
        $args['lang']         = $lang;

        return self::WX_USERINFO_URL . '?' . http_build_query($args);
    }

    /**
     * 创建获取 access_token URL
     * @param $code
     * @return string
     */
    protected function createAccessTokenUri($code)
    {
        $args['appid']      = $this->config['appid'];
        $args['secret']     = $this->config['secret'];
        $args['code']       = $code;
        $args['grant_type'] = 'authorization_code';

        return self::WX_ACCESS_TOKEN_URL . '?' . http_build_query($args);
    }

    /**
     * 发起https请求
     * @param string $url
     * @return mixed
     */
    protected function httpsRequest($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $res = curl_exec($ch);
        curl_close($ch);
        $data = json_decode($res, true);

        return $data;
    }
}